﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;

namespace System.Common
{
  public static class Converter
  {
    private static readonly string[] _trueStrings = { "1", "i", "igen", "j", "ja", "y", "yes", "true", "t", ".t." };
    private static readonly string[] _falseStrings = { "0", "n", "nem", "no", "nein", "false", "f", ".f." };

    private static readonly char[] _trueChars = { '1', 'i', 'j', 'y', 't', 'I', 'J', 'Y', 'T' };
    private static readonly char[] _falseChars = { '0', 'n', 'N', 'f', 'F' };

    public static bool CanConvertTo<T>( object value )
    {
      return CanConvertTo( value, typeof( T ) );
    }

    public static bool CanConvertTo( object value, Type targetType )
    {
      var is_nullable_target = targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof( Nullable<> ).GetGenericTypeDefinition();

      if ( value == null || DBNull.Value.Equals( value ) )
      {
        if ( targetType.IsValueType )
        {
          if ( is_nullable_target )
            return true;
        }
        else
          return true;
      }

      if ( targetType == Types.ObjectType )
        return true;

      // ReSharper disable once PossibleNullReferenceException
      var value_type = value.GetType();

      if ( targetType.IsAssignableFrom( value_type ) )
        return true;

      targetType = is_nullable_target ? Nullable.GetUnderlyingType( targetType ) : targetType;

      if ( targetType.IsEnum )
      {
        if ( CanConvertByEnumConverter( value, targetType ) )
          return true;
      }

      if ( CanConvertByTypeConverter( value, value_type, targetType ) )
        return true;

      if ( CanConvertByIConvertible( value ) )
        return true;

      return CanConvertByOperator( value_type, targetType ) || CanConvertBySpecial( value_type, targetType );
    }

    public static T ConvertTo<T>( object value )
    {
      return (T) ConvertTo( value, typeof( T ) );
    }

    public static object ConvertTo( object value, Type targetType )
    {
      object result = null;
      var state = ConvertTo( value, targetType, ref result );

      if ( state )
        return result;

      throw new InvalidCastException();
    }

    public static bool TryConvertTo<T>( object value, ref T converted )
    {
      object result = null;
      var state = ConvertTo( value, typeof( T ), ref result );

      if ( !state )
        return false;

      converted = (T) result;
      return true;
    }



    private static bool ConvertTo( object value, Type targetType, ref object result )
    {
      var is_nullable_target = targetType.IsGenericType && targetType.GetGenericTypeDefinition() == typeof( Nullable<> ).GetGenericTypeDefinition();

      if ( value == null || DBNull.Value.Equals( value ) )
      {
        if ( targetType.IsValueType )
        {
          if ( !is_nullable_target )
            return false;

          result = null;
          return true;
        }

        result = null;
        return true;

      }

      if ( targetType == Types.ObjectType )
      {
        result = value;
        return true;
      }

      var value_type = value.GetType();

      if ( targetType.IsAssignableFrom( value_type ) )
      {
        result = value;
        return true;
      }

      targetType = is_nullable_target ? Nullable.GetUnderlyingType( targetType ) : targetType;

      if ( targetType.IsEnum )
      {
        if ( ConvertByEnumConverter( value, targetType, ref result ) )
          return true;
      }

      if ( ConvertByTypeConverter( value, value_type, ref result, targetType ) )
        return true;

      if ( ConvertByIConvertible( value, ref result, targetType ) )
        return true;

      return ConvertByOperator( value, value_type, ref result, targetType ) || ConvertBySpecial( value, value_type, ref result, targetType );
    }

    private static bool CanConvertByEnumConverter( object value, Type targetType )
    {
      try
      {
        var s_value = value.ToString();
        if ( string.IsNullOrEmpty( s_value ) )
          return false;

        try
        {
          if ( Enum.IsDefined( targetType, s_value ) )
            return true;
        }
        catch ( Exception ) { }

        var i_value = 0;

        if ( int.TryParse( s_value, out i_value ) )
        {
          if ( Enum.IsDefined( targetType, i_value ) )
            return true;
        }

        if ( Enum.IsDefined( targetType, value ) )
          return true;
      }
      catch ( Exception ) { }

      return false;
    }

    private static bool ConvertByEnumConverter( object value, Type targetType, ref object result )
    {
      try
      {
        var s_value = value.ToString();
        if ( string.IsNullOrEmpty( s_value ) )
          return false;

        try
        {
          result = Enum.Parse( targetType, s_value, true );
          return true;
        }
        catch ( Exception ) { }

        var i_value = 0;

        if ( int.TryParse( s_value, out i_value ) )
        {
          result = Enum.ToObject( targetType, i_value );
          return true;
        }

        result = Enum.ToObject( targetType, value );
        return true;
      }
      catch ( Exception ) { }

      return false;
    }

    private static bool CanConvertByTypeConverter( object value, Type valueType, Type targetType )
    {
      var converter = TypeDescriptor.GetConverter( targetType );

      if ( converter.CanConvertFrom( valueType ) )
        return true;

      converter = TypeDescriptor.GetConverter( value );
      return converter.CanConvertTo( targetType );
    }

    private static bool ConvertByTypeConverter( object value, Type valueType, ref object result, Type targetType, CultureInfo culture = null )
    {
      var converter = TypeDescriptor.GetConverter( targetType );

      if ( converter.CanConvertFrom( valueType ) )
      {
        try
        {
          result = converter.ConvertFrom( null, culture ?? CultureInfo.CurrentCulture, value );
          return true;
        }
        catch ( Exception ) { }
      }

      converter = TypeDescriptor.GetConverter( value );
      try
      {
        if ( converter.CanConvertTo( targetType ) )
        {
          result = converter.ConvertTo( null, culture ?? CultureInfo.CurrentCulture, value, targetType );
          return true;
        }
      }
      catch { }

      return false;
    }

    private static bool CanConvertByIConvertible( object value )
    {
      var converter = value as IConvertible;

      return converter != null;
    }

    private static bool ConvertByIConvertible( object value, ref object result, Type targetType, CultureInfo culture = null )
    {
      var converter = value as IConvertible;

      if ( converter == null )
        return false;

      try
      {
        if ( Types.BoolType.Equals( targetType ) )
        {
          result = converter.ToBoolean( culture ?? CultureInfo.CurrentCulture );
          return true;
        }

        if ( Types.ByteType.Equals( targetType ) )
        {
          result = converter.ToByte( culture ?? CultureInfo.CurrentCulture );
          return true;
        }

        if ( Types.CharType.Equals( targetType ) )
        {
          result = converter.ToChar( culture ?? CultureInfo.CurrentCulture );
          return true;
        }

        if ( Types.DateTimeType.Equals( targetType ) )
        {
          result = converter.ToDateTime( culture ?? CultureInfo.CurrentCulture );
          return true;
        }

        if ( Types.DecimalType.Equals( targetType ) )
        {
          result = converter.ToDecimal( culture ?? CultureInfo.CurrentCulture );
          return true;
        }

        if ( Types.DoubleType.Equals( targetType ) )
        {
          result = converter.ToDouble( culture ?? CultureInfo.CurrentCulture );
          return true;
        }

        if ( Types.ShortType.Equals( targetType ) )
        {
          result = converter.ToInt16( culture ?? CultureInfo.CurrentCulture );
          return true;
        }

        if ( Types.IntType.Equals( targetType ) )
        {
          result = converter.ToInt32( culture ?? CultureInfo.CurrentCulture );
          return true;
        }

        if ( Types.LongType.Equals( targetType ) )
        {
          result = converter.ToInt64( culture ?? CultureInfo.CurrentCulture );
          return true;
        }

        if ( Types.SbyteType.Equals( targetType ) )
        {
          result = converter.ToSByte( culture ?? CultureInfo.CurrentCulture );
          return true;
        }

        if ( Types.FloatType.Equals( targetType ) )
        {
          result = converter.ToSingle( culture ?? CultureInfo.CurrentCulture );
          return true;
        }

        if ( Types.UshortType.Equals( targetType ) )
        {
          result = converter.ToUInt16( culture ?? CultureInfo.CurrentCulture );
          return true;
        }

        if ( Types.UintType.Equals( targetType ) )
        {
          result = converter.ToUInt32( culture ?? CultureInfo.CurrentCulture );
          return true;
        }

        if ( Types.UlongType.Equals( targetType ) )
        {
          result = converter.ToUInt64( culture ?? CultureInfo.CurrentCulture );
          return true;
        }

        result = converter.ToType( targetType, culture ?? CultureInfo.CurrentCulture );
        return true;
      }
      catch { }

      return false;
    }

    private static bool CanConvertByOperator( Type valueType, Type targetType )
    {
      var operators = targetType.GetMethods( BindingFlags.Static | BindingFlags.Public )
                                  .Where( m => m.Name == "op_Explicit" || m.Name == "op_Implicit" )
                                    .Where( m => m.ReturnType == targetType )
                                      .Where( m => m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType == valueType );
      return operators.Any();
    }

    private static bool ConvertByOperator( object value, Type valueType, ref object result, Type targetType )
    {
      var operators = targetType.GetMethods( BindingFlags.Static | BindingFlags.Public )
                                  .Where( m => m.Name == "op_Explicit" || m.Name == "op_Implicit" )
                                    .Where( m => m.ReturnType == targetType )
                                      .Where( m => m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType == valueType );

      foreach ( var op in operators )
      {
        try
        {
          result = op.Invoke( null, new[] { value } );
          return true;
        }
        catch { }
      }

      return false;
    }

    private static bool CanConvertBySpecial( Type valueType, Type targetType )
    {
      if ( Types.GuidType == targetType )
      {
        if ( Types.StringType == valueType )
          return true;

        if ( Types.ByteArrayType == valueType )
          return true;

        return false;
      }

      if ( Types.BoolType != targetType )
        return false;

      if ( Types.StringType == valueType )
        return true;

      return Types.CharType == valueType;
    }

    private static bool ConvertBySpecial( object value, Type valueType, ref object result, Type targetType )
    {
      if ( Types.GuidType == targetType )
      {
        try
        {
          if ( Types.StringType == valueType )
          {
            Guid guid;
            if ( Guid.TryParse( value.ToString(), out guid ) )
            {
              result = guid;
              return true;
            }
          }

          if ( Types.ByteArrayType == valueType )
          {
            result = new Guid( (byte[]) value );
            return true;
          }
        }
        catch { }

        return false;
      }

      if ( Types.BoolType != targetType )
        return false;

      if ( Types.StringType == valueType )
      {
        if ( _trueStrings.Contains( value.ToString(), StringComparer.InvariantCultureIgnoreCase ) )
        {
          result = true;
          return true;
        }

        if ( !_falseStrings.Contains( value.ToString(), StringComparer.InvariantCultureIgnoreCase ) )
          return false;

        result = false;
        return true;
      }

      if ( Types.CharType != valueType )
        return false;

      var ch = Convert.ToChar( value );
      if ( _trueChars.Contains( ch ) )
      {
        result = true;
        return true;
      }

      if ( !_falseChars.Contains( ch ) )
        return false;

      result = false;
      return true;
    }

  }
}
